package com.jf.anmocker.plugin.mockcore.javassist

import com.jf.anmocker.plugin.injector.AnoEntity
import com.jf.mocker.anotations.IMethodMocker
import com.jf.mocker.anotations.MethodMachWay
import javassist.*

class MethodMockerManager(private val mClassPool : ClassPool) {

    private var mockAble : ReplaceAgentMocker = ReplaceAgentMocker()

    fun getMocker() : ReplaceAgentMocker {
        return mockAble
    }

    fun mockerClassMethods(targetCls : CtClass, anoList : MutableList<AnoEntity>) : Boolean {
        //遍历需要操作的函数
        val methodMap = collectAllAnoLinkToTargetClass(anoList)
        //修改标记
        var modified = false
        //遍历处理函数
        println("MethodMocker ---------- targetCls.declaredMethods -----------")
        methodMap.entries.forEach {
            modified = mockMethod(targetCls, it.key, it.value)
        }
//        targetCls.declaredMethods?.forEach { method ->
//            // 根据自己的需求，判断是否需要修改这个方法
//            methodMap[method.name]?.let {
//                modified = getMocker().modifyMethod(targetCls, method, ano.ctClass, it) || modified
//            } ?: println("MethodMocker >>> modifyMethod jump ： methodMap[${method.name}] not exists")
//        }
        return modified
    }

    private fun mockMethod(targetCls : CtClass, methodName : String, anoList: MutableList<MethodAnoEntity>) : Boolean{
        //排序，优先处理replace,最后处理before,after
        anoList.sortedWith(Comparator { p0, p1 ->
            if(p0 == null || p1 == null){
                return@Comparator 0
            }
            val sort = p0.methodMocker.priority - p1.methodMocker.priority
            if(sort == 0){
                return@Comparator p1.methodMocker.mockType.value - p0.methodMocker.mockType.value
            }else{
                return@Comparator sort
            }
        })
        //开始处理函数
        var mockedFlag = false
        anoList.forEach {
            if(it.methodMocker.params.isEmpty() && it.methodMocker.machWay == MethodMachWay.MUTABLE){
                getDeclaredMethods(targetCls, methodName)?.forEach { targetMethod ->
                    mockedFlag = getMocker().modifyMethod(targetCls, targetMethod, it) || mockedFlag
                }
            }else{
                getDeclaredMethod(mClassPool, targetCls, methodName, it.methodMocker.params)?.let { targetMethod ->
                    mockedFlag = getMocker().modifyMethod(targetCls, targetMethod, it) || mockedFlag
                }
            }
        }
        return mockedFlag
    }

    private fun collectAllAnoLinkToTargetClass(anoList : MutableList<AnoEntity>) : HashMap<String, MutableList<MethodAnoEntity>>{
        val methodMap = HashMap<String, MutableList<MethodAnoEntity>>()
        for (ano in anoList){
            ano.ctClass.methods?.forEach { anoMethod ->
                val methodMocker = anoMethod.getAnnotation(IMethodMocker::class.java)
                if(methodMocker is IMethodMocker){
                    if(methodMap[methodMocker.name] != null){
                        methodMap[methodMocker.name]?.add(MethodAnoEntity(methodMocker.name, methodMocker, ano.ctClass, anoMethod))
                    }else{
                        methodMap[methodMocker.name] = mutableListOf(MethodAnoEntity(methodMocker.name, methodMocker, ano.ctClass, anoMethod))
                    }
                    println("MethodMocker >>> add MethodEntity ${methodMocker.name} : ${methodMocker.mockType}")
                }
            }
        }
        return methodMap
    }

    companion object{
        fun getDeclaredMethods(targetCls: CtClass, methodName: String) : Array<CtMethod>?{
            try {
                return targetCls.getDeclaredMethods(methodName)
            }catch (e : NotFoundException){

            }
            return null
        }

        fun getDeclaredMethod(mClassPool : ClassPool, targetCls: CtClass, methodName: String, params: Array<String> = emptyArray()) : CtMethod?{
            val paramClasses = mutableListOf<CtClass>()
            params.forEach {
                mClassPool.getOrNull(it)?.let { cls ->
                    paramClasses.add(cls)
                }
            }
            return getDeclaredMethod(targetCls, methodName, paramClasses.toTypedArray())
        }

        fun getDeclaredMethod(targetCls: CtClass, methodName: String, params: Array<CtClass> = emptyArray()) : CtMethod?{
            try {
                if(params.isEmpty()){
                    return targetCls.getDeclaredMethod(methodName)
                }
                return targetCls.getDeclaredMethod(methodName, params)
            }catch (e : NotFoundException){

            }
            return null
        }

        fun getDeclaredFiled(targetCls: CtClass, filedName: String) : CtField?{
            try {
                if(filedName.isNotEmpty()){
                    return targetCls.getDeclaredField(filedName)
                }
            }catch (e : NotFoundException){

            }
            return null
        }
    }
}

data class MethodAnoEntity(val methodName : String, val methodMocker : IMethodMocker,val anoClz: CtClass, val method : CtMethod)